package vsa.migrations.junit.migrators;

import java.util.Collection;
import java.util.List;

import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.emftext.language.java.classifiers.Class;
import org.emftext.language.java.classifiers.ConcreteClassifier;
import org.emftext.language.java.commons.NamedElement;
import org.emftext.language.java.containers.CompilationUnit;
import org.emftext.language.java.expressions.Expression;
import org.emftext.language.java.instantiations.ExplicitConstructorCall;
import org.emftext.language.java.literals.BooleanLiteral;
import org.emftext.language.java.literals.Super;
import org.emftext.language.java.members.Constructor;
import org.emftext.language.java.parameters.Parameter;
import org.emftext.language.java.types.Boolean;
import org.emftext.language.java.types.Type;

import vsa.migrations.junit.JunitMigrationException;

/**
 * contains all VSA-speficic migrations for junit4
 * - Mapping Baseclass to interceptor
 *
 * - check constructor to call super
 * - get fitting superconstructor
 *
 *   - is classAbstractMarvinTestCase and parameters String/boolean
 * @author oleym
 *
 */
public class VSAJunitClassMigrator extends AbstractJunitMigrator  {


	@Override
	public void cleanupTestCase(CompilationUnit compilationunit, Class clazz) throws JunitMigrationException {

	}


	public String getID (final Collection <Type> type) {
		String id = "";
		for (Type nextType: type) {
			if (nextType instanceof Boolean)
				id+= "boolean";
			else if (nextType instanceof NamedElement)  {
				NamedElement namedElement = (NamedElement) nextType;
				if (namedElement.getName().equals("Boolean"))
					id+= "boolean";
				else
				  id += namedElement.getName();

			}

			id += "#";

		}

		return id;
	}


	public EList <Type> getTypesOfMethod (Constructor constructor) {
		EList <Type> types = new BasicEList<Type>();
		for (Parameter nextParam: constructor.getParameters()) {
			types.add(nextParam.getTypeReference().getTarget());
		}

		return types;

	}

	public Constructor getSuperConstructorsCalled (final Class superclass, final ExplicitConstructorCall call) {

		EList<Type> argumentTypes = call.getArgumentTypes();

		List<Constructor> constructors = migrationutil.getConstructors(superclass);
		for (Constructor nextConstructor : constructors) {
			List<Type> types = getTypesOfMethod(nextConstructor);

			if (types.size() != argumentTypes.size())
				continue;

			String id1 = getID(types);
			String id2 = getID(argumentTypes);

			if (id1.equals(id2))
				return nextConstructor;

		}

		throw new IllegalStateException("Super-constructor with arguments " + argumentTypes + " not found in superclass" + superclass.getName());


	}


	/**
	 * determines if constructor call is initializing database
	 * @param call call
	 * @return true/false
	 */
	public boolean isDbInitializingTest (final Class clazz, final Constructor constructor, final Class classAbstractMarvinTestCase) {

			EList<Expression> childrenExpressions = constructor.getChildrenByType(Expression.class);

			Class superClass = (Class) clazz.getExtends().getTarget();

			if (childrenExpressions.size() > 0 && childrenExpressions.get(0) instanceof ExplicitConstructorCall) {
			  ExplicitConstructorCall call = (ExplicitConstructorCall) childrenExpressions.get(0);

		      if (superClass.equals(classAbstractMarvinTestCase)) {
		    	  if (! (call.getCallTarget() instanceof Super))
		    		  return false;
		    	  
		    	  if (call.getArguments().isEmpty()) //default super call 
		    		  return true;

		    	  if (call.getArguments().size() == 1) { //If constructor without name was used
		    		  Expression expression = call.getArguments().get(0);
				      if (expression instanceof BooleanLiteral) {
					    BooleanLiteral initializeBoolean = (BooleanLiteral) expression;
					    return initializeBoolean.isValue();
				      }  
				      else
				    	return false;
		    	  }
		    	  else {
		    	    Expression expression = call.getArguments().get(1); //If constructor with name was used
		    	    if (expression instanceof BooleanLiteral) {
					  BooleanLiteral initializeBoolean = (BooleanLiteral) expression;
					  return initializeBoolean.isValue();
				    }  
				    else
				      return false;
		    	  }
		      }
		      else if (clazz.getExtends() != null){
		    	    Constructor calledConstructor = getSuperConstructorsCalled(superClass, call);
		    	    boolean superIsDbIntializingTest = isDbInitializingTest(superClass, calledConstructor, classAbstractMarvinTestCase);
		    	    if (superIsDbIntializingTest)
		    		  return true;
		    	  }
		      }

		return false;

	}

	@Override
	public void migrateTestCase(CompilationUnit compilationunit, Class clazz) throws JunitMigrationException {
        //if class extends AbstractWorkflowTestCase with have to add an annotation @WfConf
		Class classWorkflowTestCase = (Class) getClassifierAbstractWorkflowTestCase(compilationunit);
		if (migrationutil.extendsClass(clazz, classWorkflowTestCase)) {
			migrationutil.addAnnotationOnDemand(clazz, getClassifierWfConf(compilationunit));
		}

		//if we call AbstractTestCase(String anything, true) we add an annotation @DbConf
		Class classAbstractMarvinTestCase = (Class) getClassifierAbstractMarvinTestCase(compilationunit);
		if (migrationutil.extendsClass(clazz, classAbstractMarvinTestCase)) {
			for (Constructor constructor : migrationutil.getConstructors(clazz)) {
				if (isDbInitializingTest(clazz, constructor, classAbstractMarvinTestCase)) {
					migrationutil.addAnnotationOnDemand(clazz, getClassifierDbConf(compilationunit));
					break;
				}
			}
		}
	}


	public static ConcreteClassifier getClassifierDbConf (CompilationUnit compilationunit) {
		return compilationunit.getConcreteClassifier("vsa.marvin.test.DbConf");
	}

	public static ConcreteClassifier getClassifierWfConf (CompilationUnit compilationunit) {
		return compilationunit.getConcreteClassifier("vsa.marvin.test.WfConf");
	}

	public static ConcreteClassifier getClassifierAbstractWorkflowTestCase (CompilationUnit compilationunit) {
		return compilationunit.getConcreteClassifier("vsa.marvin.gui.swing.develop.AbstractWorkflowTestCase");
	}

	public static ConcreteClassifier getClassifierAbstractMarvinTestCase (CompilationUnit compilationunit) {
		return compilationunit.getConcreteClassifier("vsa.marvin.test.AbstractTestCase");
	}

}
